package cn.liuxiany.alibaba.provider.dubbo;

import cn.liuxiany.alibaba.provider.api.HelloService;
import cn.liuxiany.alibaba.provider.entity.AccountEntity;
import cn.liuxiany.alibaba.provider.mapper.AccountMapper;
import cn.liuxiany.alibaba.storage.api.StorageService;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.shardingsphere.transaction.annotation.ShardingTransactionType;
import org.apache.shardingsphere.transaction.core.TransactionType;
import org.apache.shardingsphere.transaction.core.TransactionTypeHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.transaction.annotation.Transactional;

import java.net.InetSocketAddress;

/**
 * 实现service-one中定义的接口
 * 注意@Service注解是org.apache.dubbo.config.annotation下的，不是org.springframework.stereotype下的
 *
 * @author liuxiany
 * @date 2020/07/05
 */
@Slf4j
@DubboService
public class HelloServiceImpl implements HelloService {

    private static final String X_REQUEST_IP = "X-Request-Id";// nginx需要配置

    @Value("${spring.application.name}")
    private String appName;

    @Value("${server.port}")
    private String port;

    /*@DubboReference(loadbalance = "roundrobin", timeout = 5000, check = false, mock = "cn.liuxiany.alibaba.provider.mock.MultServiceMock")
    private MultService multService;*/

    @DubboReference(loadbalance = "roundrobin", timeout = 5000, check = false, mock = "cn.liuxiany.alibaba.storage.mock.StorageServiceMock")
    private StorageService storageService;

    @Autowired
    private AccountMapper accountMapper;

    @Override
    public String hello(String name) {
//        Assert.isTrue(StringUtils.hasText(name), "姓名可不能为空啊");
        log.info("hello: {}", name);
        InetSocketAddress remoteAddress = RpcContext.getContext().getRemoteAddress();

        String requestId = RpcContext.getContext().getAttachment(X_REQUEST_IP);
        log.info("requestId is {}", requestId);

        // 有mock情况下：当consumer找不到provider时，会进入mock
        // 无mock情况下：当consumer找不到provider时，会进入consumer端的全局异常处理

        // 模拟异常
        // 有和无mock情况下:出现异常时，consumer会捕捉到异常，经过consumer端的全局异常处理进行了拦截处理，provider端则捕捉不到异常，不会进入全局异常处理。推测跟rpc调用有关。
        if ("jack".equals(name)) {
            String s = null;
            s = s.substring(0);
        }

        // 模拟超时20s
        // 有mocker情况下：超时会进入Mock。前提是consumer的timeout小于gateway的timeout时间限制,重试两次的时间之和小于gateway的时间限制才会进入mock。否则依旧会进入gateway的fallback。
        // 无mocker情况下：超时，consumer端会默认进行2次重试，会触发gateway端timelimiter的时间限制，而产生TimeoutException，会进入gateway端的fallback。
/*        if ("jack".equals(name)) {
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }*/

//        RpcContext.getContext().setAttachment(X_REQUEST_IP, requestId);
//        multService.hello2(name);

        // 获得来自provider2的参数
//        String hello2 = RpcContext.getServerContext().getAttachment("hello2");
//        log.info("result from provider22222:{}", hello2); //经测试，获取不到。

        // 向consumer写回参数
        RpcContext.getServerContext().setAttachment("hello", "hello world haha");

//        storageService.storage("hello storage");

        return String.format("hello :%s,this is %s:%s, come from %s", name, appName, port, remoteAddress);
    }


    /**
     * 模式测试分布式事务
     *
     * @param orderId
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
//    @ShardingTransactionType(TransactionType.BASE)
    @GlobalTransactional(rollbackFor = Exception.class) // seata的注解，事务发起方（TM）加这个注解，其他服务不需要加
    public String addUserScore(String orderId) {
        TransactionTypeHolder.set(TransactionType.BASE);
        // 模拟新增
//        AccountEntity account = new AccountEntity();
//        account.setName("测试分布式事务");
//        account.setOrderId(orderId);
//        account.setScore(101);
//
//        accountMapper.insert(account);

        // 模拟更新
        LambdaUpdateWrapper<AccountEntity> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(AccountEntity::getName, "测试分布式事务更新-ex");
        updateWrapper.eq(AccountEntity::getId, "1727934055057567746");

        accountMapper.update(updateWrapper);


        // 模拟provider端异常
//        String s = null;
//        s.substring(0);

        // 模拟超时
        // 1 如果consumer的timeout大于超时时间的话，则正常进行二阶段提交事务。consumer和provider端的事务都正常提交。
        // 2.1如果consumer的timeout小于超时时间的话，consumer端的会提交，provider端的事务因为xid不存在，而导致事务提交失败。会进入dubbo的Mock。
        // 2.2 provider端的异常为org.springframework.transaction.TransactionSystemException: JDBC commit failed; nested exception is java.sql.SQLException: io.seata.core.exception.RmTransactionException:
        // Response[ TransactionException[Could not found global transaction xid = 192.168.56.1:8091:1495657036383674769, may be has finished.] ]
        // 3 如果consumer端没有设置timeout的话，consumer端的会提交，provider端的事务因为xid不存在，而导致事务提交失败。会进入dubbo的Mock。
/*        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }*/

        return "provider端调用成功";
    }
}
